package service

import (
	"context"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	"wxpay/internal/dao"
	"wxpay/internal/dto"
	"wxpay/internal/model"
	"wxpay/pkg/mcontext"
	"wxpay/pkg/merrors"
	"wxpay/pkg/util"

	"github.com/go-redis/redis/v8"
	"go.uber.org/zap"
	"golang.org/x/sync/errgroup"

	"github.com/bsm/redislock"

	"gorm.io/gorm"

	mchApi "wxpay/pkg/wechat_api"
)

type transferService struct {
	d dao.DataStore
	r *redis.Client
}

func NewTransferService(d dao.DataStore, r *redis.Client) *transferService {
	return &transferService{d: d, r: r}
}

// 受理转账，落地转账明细单（幂等保证，可重入）
func (s *transferService) CreateTransferDetail(ctx context.Context, detail *dto.TransferDetailDTO) (dt *dto.TransferDetailDTO, err error) {
	const op merrors.Op = "transService.CreateTransferDetail"
	repo := s.d.Transfer()
	m, err := repo.QueryDetailByOutDetailNo(ctx, s.d.GetTx(ctx), detail.BizDetailNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 不存在，创建明细单
		m = dto.AdaptTransferDetail(detail)
		m.Initialize()
		if err := s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
			if e := repo.SaveDetail(ctx, tx, m); e != nil {
				return e
			}
			return nil
		}); err != nil {
			return nil, err
		}
	} else if err != nil {
		return nil, merrors.E(op, merrors.Invalid, err)
	}
	dt = dto.ConvertTransferDetail(m)
	return dt, nil
}

// 根据业务单号查询转账单详情
func (s *transferService) TransferDetailQueryByBizNo(ctx context.Context, bizNo string) (detail *dto.TransferDetailDTO, err error) {
	const op merrors.Op = "transService.TransferDetailQueryByBizNo"
	repo := s.d.Transfer()
	m, err := repo.QueryDetailByOutDetailNo(ctx, s.d.GetTx(ctx), bizNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		// 不存在，报错
		err = merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "转账单不存在")
		return
	}
	if err != nil {
		err = merrors.E(op, merrors.Invalid, err)
		return
	}
	detail = dto.ConvertTransferDetail(m)
	return
}

// 化零为整，生成批次转账单 -- 定时触发
func (s *transferService) BatchCreate(ctx context.Context) (err error) {
	const op merrors.Op = "transService.BatchCreate"
	// 获取分布式锁, 依靠分布式锁保证不重复生成批次号
	locker := redislock.New(s.r)
	lock, err := locker.Obtain(ctx, "wxpay-bt", 10*time.Second, nil)
	if err == redislock.ErrNotObtained {
		return merrors.E(op, merrors.Invalid, merrors.ErrAcquireLock, "获取dist锁失败")
	} else if err != nil {
		return merrors.E(op, merrors.Invalid, err, "redis error")
	}
	// defer Release lock
	defer lock.Release(ctx)
	n := util.GetNow()
	bg := model.GenBatchGroup(n)

	details, err := s.d.Transfer().QueryAwaitDetails(ctx, s.d.GetTx(ctx), bg)
	collection := make(map[string][]*model.TransferDetail)
	transfers := make(map[string][]*model.Transfer)
	for _, d := range details {
		// 按照appID分组
		collection[d.AppID] = append(collection[d.AppID], d)
	}
	for appID, ds := range collection {
		// 每个批次最多100条明细
		m := make(map[int]string)
		for i, d := range ds {
			index := i / 100
			if m[index] == "" {
				// 生成批次单
				m[index] = model.GenBatchNo(util.GetNow())
				transfers[appID] = append(transfers[appID], model.NewTransfer(d, m[index]))
			}
			trans := transfers[appID][index]
			// 更新明细的批次号
			batchNo := m[index]
			d.UpdateOutBatchNo(batchNo)
			// 更新批次上的金额和笔数
			trans.AddDetail(d)
		}
	}
	// 持久化，保存transfer和details
	if err = s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		for _, ts := range transfers {
			for _, tr := range ts {
				if e := s.d.Transfer().SaveTransfer(ctx, tx, tr); e != nil {
					return e
				}
				// 保存消息
				if e := s.createTransferTask(ctx, tx, tr); e != nil {
					return e
				}
			}
		}
		for _, d := range details {
			if e := s.d.Transfer().SaveDetail(ctx, tx, d); e != nil {
				return e
			}
		}

		return nil
	}); err != nil {
		return err
	}
	return nil
}

func (s *transferService) createTransferTask(ctx context.Context, tx *gorm.DB, tr *model.Transfer) error {
	payload := model.NewTransferCreatedAction(tr)
	ps, _ := json.Marshal(&payload)
	msg := model.NewMessage(model.TopicTransferAction, ps, 0)
	if e := s.d.EventMessage().SaveMessageInTx(ctx, tx, msg); e != nil {
		return e
	}
	return nil
}

// 构造批量转账请求
func buildTransferRequest(t *model.Transfer, ds []*model.TransferDetail) (r mchApi.BatchTransferRequest) {
	details := make([]mchApi.TransferDetail, len(ds))
	for i, d := range ds {
		details[i] = mchApi.TransferDetail{
			OutDetailNo:    d.OutDetailNo,
			TransferAmount: uint(d.Amount),
			TransferRemark: d.Remark,
			OpenID:         d.OpenID,
			UserName:       d.UserName,
			// Fixme 暂不支持身份证参数
			// UserIDCard:     util.ConvertNullString(d.UserIDCard),
		}
	}
	r = mchApi.BatchTransferRequest{
		AppID:              t.AppID,
		OutBatchNo:         t.OutBatchNo,
		BatchName:          t.Name,
		BatchRemark:        t.Remark,
		TotalAmount:        uint(t.Amount),
		TotalNum:           uint(t.Count),
		TransferDetailList: details,
	}
	return
}

// 批量转账
func (s *transferService) BatchTransfer(ctx context.Context, outBatchNo string) (err error) {
	const op merrors.Op = "transferService.BatchTransfer"
	log := mcontext.Logger(ctx)
	transfer, err := s.d.Transfer().QueryTransferByOutBatchNo(ctx, s.d.GetTx(ctx), outBatchNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		return merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "批量转账单不存在")
	}
	if err != nil {
		return
	}
	if err := s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		tr, e := s.d.Lock().LockTransfer(ctx, tx, transfer.ID)
		if e != nil {
			return e
		}
		// 判断转账单状态, 非待发起状态直接略过
		if !tr.CanTransfer() {
			log.Info(fmt.Sprintf("单号:%s,当前状态:%s,不允许转账", outBatchNo, tr.Status))
			return nil
		}
		// 调微信接口转账
		details, e := s.d.Transfer().QueryDetailsByOutBatchNo(ctx, tx, tr.OutBatchNo, int(tr.Count))
		if e != nil {
			return e
		}
		req := buildTransferRequest(tr, details)
		client, e := NewMchService(s.d).BuildMchApiClient(ctx, tr.MchID)
		if e != nil {
			return e
		}
		resp, e := client.BatchTransfer(ctx, req)
		if e != nil {
			// 微信接口报错处理
			// 提取明细单
			fList := make([]string, len(details))
			for i, d := range details {
				fList[i] = d.OutDetailNo
			}
			// 更新批次上的统计信息
			summary := model.TransferSummary{
				SuccessNum:    0,
				SuccessAmount: 0,
				FailNum:       tr.Count,
				FailAmount:    tr.Amount,
			}
			tr.UpdateFinalState(model.TransferStatusClosed, &summary, e.Error())
			// 持久化
			if se := s.d.Transfer().SaveTransfer(ctx, tx, tr); se != nil {
				return se
			}
			if se := s.d.Transfer().BatchUpdateDetailStatus(ctx, tx, outBatchNo, model.DetailStatusFAIL, fList); se != nil {
				return se
			}
			// 发送转账结果消息
			se := s.createNotify(ctx, tx, tr, nil, fList)
			if se != nil {
				return se
			}
			return nil
		}
		// 更新转账单状态
		tr.AfterTransferSuccess(resp.BatchID)
		if e := s.d.Transfer().SaveTransfer(ctx, tx, tr); e != nil {
			return e
		}
		// 建立转账查询任务
		if e := s.createQueryTask(ctx, tx, tr); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return
}

// 对最终状态的分账明细按照状态分类
func classifyDetailsByStatus(details []mchApi.TransferDetailItem) (sList []string, fList []string) {
	for _, d := range details {
		switch d.Status {
		case model.DetailStatusSuccess:
			sList = append(sList, d.OutDetailNo)
		case model.DetailStatusFAIL:
			fList = append(fList, d.OutDetailNo)
		}
	}
	return
}

// 查询并同步转账状态
func (s *transferService) BatchTransferSync(ctx context.Context, outBatchNo string) (err error) {
	const op merrors.Op = "transferService.BatchTransferSync"
	log := mcontext.Logger(ctx)
	transfer, err := s.d.Transfer().QueryTransferByOutBatchNo(ctx, s.d.GetTx(ctx), outBatchNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		return merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "批量转账单不存在")
	}
	if err != nil {
		return
	}
	if transfer.IsFinalState() {
		// 已是最终状态，直接返回
		return nil
	}
	if err := s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		tr, e := s.d.Lock().LockTransfer(ctx, tx, transfer.ID)
		if e != nil {
			return e
		}
		// 判断转账单状态, 非待同步状态直接略过
		if !tr.IsAwaitSync() {
			log.Info(fmt.Sprintf("单号:%s,当前状态:%s,不允许转账单同步", outBatchNo, tr.Status))
			return nil
		}
		// 调微信接口查询转账状态
		req := mchApi.BatchTransferQueryByOutNoRequest{
			OutBatchNo:      tr.OutBatchNo,
			NeedQueryDetail: true,
			Offset:          0,
			Limit:           100,
			DetailStatus:    "ALL",
		}
		client, e := NewMchService(s.d).BuildMchApiClient(ctx, tr.MchID)
		if e != nil {
			return e
		}
		resp, e := client.BatchTransferQueryByOutNo(ctx, req)
		if e != nil {
			return e
		}
		var sList []string
		var fList []string

		// 更新批次单状态和明细状态
		switch resp.TransferBatch.BatchStatus {
		case model.TransferStatusAccept, model.TransferStatusProcessing:
			// 批次受理成功, 转账中
			tr.UpdateInterState(resp.TransferBatch.BatchStatus)
		case model.TransferStatusFinished, model.TransferStatusClosed:
			summary := model.TransferSummary{
				SuccessNum:    resp.TransferBatch.SuccessNum,
				SuccessAmount: resp.TransferBatch.SuccessAmount,
				FailNum:       resp.TransferBatch.FailNum,
				FailAmount:    resp.TransferBatch.FailAmount,
			}
			tr.UpdateFinalState(resp.TransferBatch.BatchStatus, &summary, resp.TransferBatch.CloseReason)
			// 所有明细皆已处理，明细可能有成功有失败, 已关闭，明细可能有成功有失败
			sList, fList = classifyDetailsByStatus(resp.TransferDetails)
		}
		// 持久化
		switch resp.TransferBatch.BatchStatus {
		case model.TransferStatusAccept, model.TransferStatusProcessing:
			// 批次受理成功, 转账中。需要后面再次查询, 建立转账查询任务
			if e := s.createQueryTask(ctx, tx, tr); e != nil {
				return e
			}
		}

		if e = s.d.Transfer().SaveTransfer(ctx, tx, tr); e != nil {
			return e
		}
		if len(sList) > 0 {
			if e = s.d.Transfer().BatchUpdateDetailStatus(ctx, tx, outBatchNo, model.DetailStatusSuccess, sList); e != nil {
				return e
			}
		}
		if len(fList) > 0 {
			if e = s.d.Transfer().BatchUpdateDetailStatus(ctx, tx, outBatchNo, model.DetailStatusFAIL, fList); e != nil {
				return e
			}
		}
		// 发送转账结果消息
		e = s.createNotify(ctx, tx, tr, sList, fList)
		if e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return
}

// 创建转账查询任务
func (s *transferService) createQueryTask(ctx context.Context, tx *gorm.DB, tr *model.Transfer) (err error) {
	payload := model.NewTransferQueryTask(tr)
	ps, _ := json.Marshal(&payload)
	msg := model.NewMessage(model.TopicTransferQuery, ps, 0)
	return s.d.EventMessage().SaveMessageInTx(ctx, tx, msg)
}

// 创建转账通知任务
func (s *transferService) createNotify(ctx context.Context, tx *gorm.DB, tr *model.Transfer, sList []string, fList []string) (err error) {
	successOrders, e := s.d.Transfer().QueryDetailsByOutDetailNos(ctx, tx, sList)
	if e != nil {
		return e
	}
	failedOrders, e := s.d.Transfer().QueryDetailsByOutDetailNos(ctx, tx, fList)
	if e != nil {
		return e
	}
	payload := model.NewBatchTransferNotification(tr, successOrders, failedOrders)
	ps, _ := json.Marshal(&payload)
	msg := model.NewMessage(model.TopicTransferNotify, ps, 0)
	return s.d.EventMessage().SaveMessageInTx(ctx, tx, msg)
}

// 查询并同步明细详情
func (s *transferService) TransferDetailsSync(ctx context.Context, allOrders model.TransferOrders, outBatchNo string) (err error) {
	// 查询并同步明细
	const op merrors.Op = "transferService.TransferDetailsSync"
	log := mcontext.Logger(ctx)
	transfer, err := s.d.Transfer().QueryTransferByOutBatchNo(ctx, s.d.GetTx(ctx), outBatchNo)
	if val, ok := err.(model.NotFound); ok && val.NotExist() {
		return merrors.E(op, merrors.NotExist, merrors.ErrNotFound, "批量转账单不存在")
	}
	if err != nil {
		return
	}

	client, e := NewMchService(s.d).BuildMchApiClient(ctx, transfer.MchID)
	if e != nil {
		return e
	}
	sOrders := sync.Map{}
	var g errgroup.Group

	for i := range allOrders {
		req := mchApi.BatchTransferQueryByDetailNoRequest{
			OutBatchNo:  transfer.OutBatchNo,
			OutDetailNo: allOrders[i].OutDetailNo,
		}
		g.Go(func() error {
			return getTransferDetailsInWeixinSide(ctx, client, req, &sOrders)
		})
	}
	// 等待所有请求完成
	if err := g.Wait(); err != nil {
		// alert 告警
		log.Error("查询微信转账详情失败", zap.Error(err))
	}

	if err := s.d.GetTx(ctx).Transaction(func(tx *gorm.DB) error {
		_, e := s.d.Lock().LockTransfer(ctx, tx, transfer.ID)
		if e != nil {
			return e
		}
		details, e := s.d.Transfer().QueryDetailsByOutBatchNo(ctx, tx, transfer.OutBatchNo, 100)
		if e != nil {
			return e
		}
		if e := s.saveTransferDetails(ctx, tx, details, &sOrders); e != nil {
			return e
		}
		return nil
	}); err != nil {
		return err
	}
	return nil
}

// 获取微信侧转账单详情，转账发起时间、状态、失败原因等结果组装存到Map中
func getTransferDetailsInWeixinSide(ctx context.Context, client mchApi.MerchantApiClient, req mchApi.BatchTransferQueryByDetailNoRequest, m *sync.Map) (err error) {
	resp, err := client.BatchTransferQueryByDetailNo(ctx, req)
	if err != nil {
		return err
	}
	iTime, err := util.ParseTimePtr(util.LayoutSecondWithTimeZoneAndT, resp.InitiateTime)
	if err != nil {
		return err
	}
	m.Store(req.OutDetailNo, &model.TransferDetail{InitiateTime: iTime, FailReason: util.AdaptNullString(resp.FailReason), Status: resp.DetailStatus})
	return nil
}

// 遍历转账详情，把转账结果持久化
func (s *transferService) saveTransferDetails(ctx context.Context, tx *gorm.DB, details []*model.TransferDetail, detailsResult *sync.Map) (err error) {
	for _, dt := range details {
		inter, ok1 := detailsResult.Load(dt.OutDetailNo)
		detail, ok2 := inter.(*model.TransferDetail)
		if ok1 && ok2 {
			// 更新转账结果
			dt.UpdateResult(detail)
			// 持久化
			e := s.d.Transfer().SaveDetail(ctx, tx, dt)
			if e != nil {
				return e
			}
		}
	}
	return nil
}
